/* 
 * File:   ClusteringKMeans.cpp
 * Author: rolando
 * 
 * Created on 15 de octubre de 2013, 13:08
 */

#include "ClusteringKMeans.h"
#define CACHE  99999

ClusteringKMeans::ClusteringKMeans(vector<string> files,
        int numClusters, int numDocs,
        int numAttributes, int maxCount) {
    this->files = files;
    //this->rawData = rawData;
    this->numClusters = numClusters;
    this->numDocs = numDocs;
    this->numAttributes = numAttributes;
    this->maxCount = maxCount;
    
    maxCache =0;
    if (this->numDocs > CACHE)
        maxCache = CACHE;
    else
        maxCache = this->numDocs;
    
    this->centroides = (HashSet**) malloc(sizeof (HashSet*) * numClusters);
    this->docs = (HashSet**) malloc(sizeof (HashSet*) * maxCache);
    this->indices = new int[this->numClusters];
    this->changedCluster = new int[this->numClusters];
    
}

ClusteringKMeans::~ClusteringKMeans() {
    for (int i = 0; i < this->numClusters; i++)
        centroides[i]->Destroy();
   
    for (int i = 0; i < this->maxCache; i++)
        docs[i]->Destroy();

    //for(int i = 0; i < this->numDocs; i++)
    //    delete[] this->rawData[i];
    //delete[] this->rawData;
}

HashSet* ClusteringKMeans::GetRowData(int doc) {
    HashSet* hs = new HashSet();
    hs->Load(files[doc].c_str());
    return hs;
}

HashSet* ClusteringKMeans::GetHashSet(int doc, bool* creo) {
    if (doc < CACHE) {
        *creo = false;
        return docs[doc];
    } else {
        *creo = true;
        HashSet* hs = new HashSet();
        hs->Load(files[doc].c_str());
        return hs;
    }
}

int ClusteringKMeans::MinIndex(double *distances) {
    int indexOfMin = 0;
    double smallDist = distances[0];
    for (int k = 0; k < this->numClusters; ++k) {
        if (distances[k] < smallDist) {
            smallDist = distances[k];
            indexOfMin = k;
        }
    }
    return indexOfMin;
}

bool ClusteringKMeans::IsCentroid(int doc) {
    for (int i = 0; i < this->numClusters; i++)
        if (indices[i] == doc)
            return true;
    return false;
}

bool ClusteringKMeans::AssignElem(int *clustering) {
    bool changed = false;
    Jaccard* jcd = Jaccard::getInstance();
    double distActual = 0.0;
    double distMax = 0.0;
    HashSet* hs;
    bool creo;
    //HashSet* hs = new HashSet(); //Docs
    for (int i = 0; i < this->numDocs; ++i) {
        if (!IsCentroid(i)) {
            hs = GetHashSet(i, &creo);
            distMax = jcd->Similarity(hs, centroides[clustering[i]]);
            for (int k = 0; k < this->numClusters; ++k) {
                //hs->Load(files[i].c_str()); //Docs
                //distActual = jcd->Similarity(hs,centroides[k]);//Docs
                distActual = jcd->Similarity(hs, centroides[k]);
                if (distActual > distMax) {
                    distMax = distActual;
                    if (clustering[i] != k) {
                        clustering[i] = k;
                        changedCluster[k] = 1;
                        changedCluster[clustering[i]] = 1;
                        changed = true;
                    }
                }
            }
            if (creo)
                hs->Destroy();
        }
    }
    //hs->Destroy(); //Docs
    return changed;
}

bool ClusteringKMeans::AssignElemVariosCluster(int **clustering) {
    bool changed = false;
    Jaccard* jcd = Jaccard::getInstance();
    double distActual = 0.0;
    double distMax = 0.0;
    HashSet* hs;
    bool creo;
    //HashSet* hs = new HashSet();//Docs
    for (int i = 0; i < this->numDocs; ++i) {
        if (!IsCentroid(i)) {
            hs = GetHashSet(i, &creo);
            distMax = jcd->Similarity(hs, centroides[clustering[0][i]]);
            for (int k = 0; k < this->numClusters; ++k) {
                //hs->Load(files[i].c_str());//Docs
                //distActual = jcd->Similarity(hs,centroides[k]);//Docs 
                distActual = jcd->Similarity(hs, centroides[k]);

                if (distActual > distMax) {
                    distMax = distActual;
                    if (clustering[0][i] != k) {
                        clustering[0][i] = k;
                        clustering[1][i] = -1;
                        changedCluster[k] = 1;
                        changedCluster[clustering[0][i]] = 1;
                        changed = true;
                    }
                } else if (distActual == distMax) {
                    bool ok = false;
                    int m = 0;
                    while ((!ok) && (m < this->numClusters)) {
                        if (clustering[m][i] == k)
                            break;
                        if (clustering[m][i] == -1) {
                            clustering[m][i] = k;
                            if ((m + 1) < this->numClusters)
                                clustering[m + 1][i] = -1;
                            ok = true;
                            changed = true;
                        }
                        m++;
                    }
                }
            }
            if (creo)hs->Destroy();
        }
    }
    //hs->Destroy(); //Docs
    return changed;
}

/*
bool ClusteringKMeans::Assign(int *clustering, double **centroids){
    bool changed = false;
    double *distances = new double[this->numClusters];
    for(int i = 0; i < this->numDocs; ++i){
        for(int k = 0; k < this->numClusters; ++k){
            distances[k] = ClusteringKMeans::Distance(this->rawData[i], 
                    centroids[k]);
        }
        int newCluster = MinIndex(distances);
        if(newCluster != clustering[i]){
            changed = true;
            clustering[i] = newCluster;
        }
    }
    return changed;
}
 */

int ClusteringKMeans::Busco_Barra_Final(const char* archivo) {
    int pos = 0;
    for (int k = 0; k < strlen(archivo); k++) {
        if (archivo[k] == '/') pos = k + 1;
    }
    return pos;
}

void ClusteringKMeans::SaveFiles(int** clustering, const char* RUTA_CLUSTER_CREAR,
        const char* PREFIJO_CLUSTER, const char* ARCH_CENTROIDES,
        const char* ARCH_LISTADO) {
    string stt = RUTA_CLUSTER_CREAR;
    if (Directory::Existe(stt)) Directory::Remover(stt);
    else Directory::Create(stt);
    char ruta_cluster[100];
    const char* ruta;
    FILE* archivo_centroides = fopen(ARCH_CENTROIDES, "w");
    FILE* archivo_cluster;
    FILE* archivo_listado;
    int largo_cluster = 0;
    int largo_nombre = 0;
    largo_cluster = strlen(PREFIJO_CLUSTER);
    strncpy(ruta_cluster, PREFIJO_CLUSTER, largo_cluster);
    ruta_cluster[largo_cluster] = '\0';
    for (int i = 0; i<this->numClusters; i++) {
        *ruta_cluster = 0;
        largo_cluster = strlen(PREFIJO_CLUSTER);
        fprintf((FILE*) archivo_centroides, "%s\n", this->files[this->indices[i]].c_str());
        strncpy(ruta_cluster, PREFIJO_CLUSTER, largo_cluster);
        ruta_cluster[largo_cluster] = '\0';
        stringstream stream;
        stream << i;
        string scluster = stream.str();
        strncpy(ruta_cluster + largo_cluster, scluster.c_str(), strlen(scluster.c_str()));
        largo_cluster = largo_cluster + strlen(scluster.c_str());
        ruta_cluster[largo_cluster] = '\0';
        strncpy(ruta_cluster + largo_cluster, ".txt", strlen(".txt"));
        ruta_cluster[largo_cluster + strlen(".txt")] = '\0';
        archivo_cluster = fopen(ruta_cluster, "w");
        fclose(archivo_cluster);
    }
    largo_cluster = strlen(PREFIJO_CLUSTER);
    strncpy(ruta_cluster, PREFIJO_CLUSTER, largo_cluster);
    ruta_cluster[largo_cluster] = '\0';
    fclose(archivo_centroides);
    int pos = Busco_Barra_Final(this->files[0].c_str()) + 8;
    archivo_listado = fopen(ARCH_LISTADO, "w");
    for (int i = 0; i<this->numDocs; i++) {
        ruta = this->files[i].c_str();
        int cl = 0;
        while ((cl < this->numClusters) && (clustering[cl][i] != -1)) {
            largo_cluster = strlen(PREFIJO_CLUSTER);
            stringstream stream2;
            stream2 << clustering[cl][i];
            string scluster2 = stream2.str();
            strncpy(ruta_cluster + largo_cluster, scluster2.c_str(), strlen(scluster2.c_str()));
            largo_cluster = largo_cluster + strlen(scluster2.c_str());
            ruta_cluster[largo_cluster] = '\0';
            strncpy(ruta_cluster + largo_cluster, ".txt", strlen(".txt"));
            ruta_cluster[largo_cluster + strlen(".txt")] = '\0';
            archivo_cluster = fopen(ruta_cluster, "a");
            fprintf((FILE*) archivo_cluster, "%s\n", ruta + pos);
            fclose(archivo_cluster);
            fprintf((FILE*) archivo_listado, "%s   %s\n", ruta + pos, scluster2.c_str());
            cl++;
        }
    }
    fclose(archivo_listado);
}

int **ClusteringKMeans::Cluster(bool varios_cluster) {
    bool changed = true;
    int count = 0;
    int **clustering = InitClustering();
    ClusteringKMeans::CalculateCentroid(clustering[0]);

    while (changed == true && count < this->maxCount) {
        printf("  Iteracion %d \n",count);
        ++count;
        for (int i = 0; i < this->numClusters; ++i)
            changedCluster[i] = 0;
        if (varios_cluster) changed = AssignElemVariosCluster(clustering);
        else changed = AssignElem(clustering[0]);
        ClusteringKMeans::CalculateCentroid(clustering[0]);
    }
    return clustering;
}

int **ClusteringKMeans::InitClustering() {
    int **clustering = new int*[this->numClusters];
    for (int i = 0; i < this->numClusters; ++i) {
        clustering[i] = new int[this->numDocs];
        indices[i] = -1;
    }

    for (int i = 0; i < this->numClusters; ++i) {
        for (int j = 0; j < this->numDocs; ++j) {
            clustering[i][j] = -1;
        }
    }

    int valor = this->numDocs / this->numClusters;

    for (int i = 0; i < this->numClusters; ++i) {
        int num = i * valor;
        clustering[0][num] = i;
        indices[i] = num;
        changedCluster[i] = 1;
        centroides[i] = GetRowData(num);
        if (i < CACHE)
            docs[num] = GetRowData(num);
    }
    for (int i = 0; i < this->numDocs; ++i) {
        if (!IsCentroid(i)) {
            clustering[0][i] = i % (this->numClusters);
            if (i < CACHE)
                docs[i] = GetRowData(i);
        }
    }

      /*
       for (int i = 0; i < this->numClusters; ++i) {
           clustering[0][i] = i;
           centroides[i] = GetRowData(i);
           if (i < CACHE)
               docs[i] = GetRowData(i);
       }

       for (int i = this->numClusters; i < this->numDocs; ++i) {
           //clustering[0][i] = rand() % (this->numClusters);
           clustering[0][i] = i % (this->numClusters);
           if (i < CACHE)
               docs[i] = GetRowData(i);
       }
     */
    
    return clustering;
}

double **ClusteringKMeans::Allocate() {
    double **result = new double*[this->numClusters];
    for (int i = 0; i < this->numClusters; ++i) {
        result[i] = new double[this->numAttributes];
    }
    return result;
}

void ClusteringKMeans::OptimiceCentroid(int *clustering) {
    Jaccard* jcd = Jaccard::getInstance();
    bool changed = false;
    for (int i = 0; i<this->numClusters; i++) {
        changed = false;
        for (int j = 0; j < i; j++) {
            double valor = jcd->Similarity(centroides[i], centroides[j]);
            if (valor < 0.9) {
                changed = true;
                break;
            }

        }
        if (changed) {

        }
    }
}

void ClusteringKMeans::CalculateCentroid(int *clustering) {
    //HashSet* hs0 = new HashSet();//Docs
    //HashSet* hs1 = new HashSet();//Docs
    Jaccard* jcd = Jaccard::getInstance();
    double* distDocs = new double[this->numDocs];
    double* distCluster = new double[this->numClusters];
    HashSet* hs1;
    HashSet* hs2;
    bool creo1;
    bool creo2;

    for (int i = 0; i < this->numDocs; ++i) {
        distDocs[i] = 0;
    }

    for (int i = 0; i < this->numClusters; ++i) {
        //indices[i] = -1;
        distCluster[i] = 0;
    }

    for (int i = 0; i < this->numDocs; ++i) {
        if ((changedCluster[clustering[i]] == 0))
            continue;
     
        //hs0->Load(files[i].c_str());//Docs
        hs1 = GetHashSet(i,&creo1);
        for (int j = i + 1; j< this->numDocs; j++) {
            if (clustering[i] != clustering[j])
                continue;
            //hs1->Load(files[j].c_str());//Docs
            //double valor = jcd->Similarity(hs0, hs1);//Docs
            hs2 = GetHashSet(j,&creo2);
            double valor = jcd->Similarity(hs1, hs2);
            if(creo2)
                hs2->Destroy();
            distDocs[i] += valor;
            distDocs[j] += valor;
        }
        
        //
        for (int j = 0; j<this->numClusters; j++) {
            if (clustering[i] != j) {
                double valorc = jcd->Similarity(hs1, centroides[j]);
                distDocs[i] += (1 - valorc);
            }
        }
        //
        if(creo1)
            hs1->Destroy();
        
        if ((indices[clustering[i]] < 0) || (distDocs[i] > distCluster[clustering[i]])) {
            distCluster[clustering[i]] = distDocs[i];
            indices[clustering[i]] = i;
        }
    }

    for (int i = 0; i < this->numClusters; ++i) {
        centroides[i]->Load(files[indices[i]].c_str());
    }

    //hs0->Destroy();//Docs
    //hs1->Destroy();//Docs
}

double ClusteringKMeans::Distance(int *doc, double *mean) {
    double result = 0.0;
    for (int j = 0; j < this->numAttributes; ++j) {
        result += pow((doc[j] - mean[j]), 2);
    }
    return sqrt(result);
}

/*
double *ClusteringKMeans::ComputeCentroid(int *clustering, int cluster, 
        double **means){
    
    double *centroid = new double[this->numAttributes];
    double minDist = 99999.9;
    for(int i= 0; i < this->numDocs; ++i){
        if(clustering[i] != cluster) continue;
        double currentDist = Distance(this->rawData[i], means[cluster]);
        if(currentDist < minDist){
            minDist = currentDist;
            for(int j = 0; j < this->numAttributes; ++j){
                centroid[j] = this->rawData[i][j];
            }
        }
    }
    return centroid;
}
 */

/*
void ClusteringKMeans::UpdateMeans(int *clustering, double **means) {
    for(int k = 0; k < this->numClusters; ++k)
        for(int j = 0; j < this->numAttributes; ++j)
            means[k][j] = 0.0;
    int clusterCounts[this->numClusters];
    for(int i = 0; i < this->numDocs; ++i){
        int cluster = clustering[i];
        ++clusterCounts[cluster];
        
        for(int j = 0; j < this->numAttributes; ++j){
            means[cluster][j] += this->rawData[i][j];
        }
    }
    
    for(int k = 0; k < this->numClusters; ++k)
        for(int j = 0; j < this->numAttributes; ++j)
            means[k][j] /= clusterCounts[k];
        
}
 */

/*
void ClusteringKMeans::UpdateCentroids(int *clustering, double **means, 
        double **centroids){
    for(int k = 0; k < this->numClusters; ++k){
        centroids[k] = ComputeCentroid(clustering, k, means);
    }
}
 */